feat(gcp): add Cloud SQL and GKE adapters - #155
Conversation
The GCP database cell was empty even though the runtime serves sqladmin — its
`coming_soon` was a frontend assumption, not a runtime fact. With availability
now derived from the registry, registering this adapter is all it takes.
Mirrors the public sqladmin v1beta4 contract. Two behaviours are specific to the
local runtime and worth naming:
- Create answers with a `sql#operation` receipt that names the instance but does
not embed it, so the adapter reads the instance back rather than echoing the
receipt — otherwise the operation UUID surfaces as the resource name.
- The runtime backs each instance with a real Postgres container and rejects
every other engine, so the schema offers PostgreSQL versions only rather than
the full GCP list.
adapter-gcp/operations.ts keeps the envelope discrimination in one place, since
GKE and Cloud Run return the `{done, response}` shape instead and will need the
same handling.
Verified end to end against floci-gcp 0.5.0: create starts a
postgres:15.18-alpine container and reports the instance RUNNABLE with its IP
and connection name; list, inspect and delete round-trip and leave no container
behind.
The GCP k8s cell was empty while the runtime served GKE the whole time. Backed
by a real k3s container, so clusters report a live API endpoint, master version
and node pools.
Worth recording why the path is `/container/v1/...` rather than the `/v1/...`
the other GCP services use. The runtime serves both, and
`/v1/projects/{p}/locations/{l}/clusters` — the same path shape GKE uses on
container.googleapis.com — is Managed Service for Apache Kafka here. It answers
200, returns something called a cluster, and creating one starts a Redpanda
broker. An adapter bound there would have listed Kafka brokers as Kubernetes
clusters: the exact class of dishonesty the capability guard exists to catch,
except the guard cannot see it because the schema and the adapter would agree
with each other. The distinguishing evidence was the payload (`bootstrapAddress`
on :9092, `vcpuCount`) and the container image the runtime started.
GKE brings a third operation-envelope shape — `{status, operationType,
targetLink}`, naming the cluster by path with no embedded resource — so
operations.ts now resolves a targetLink as well as a targetId.
Verified against floci-gcp 0.5.0: create starts a rancher/k3s container and the
cluster reports RUNNING with its endpoint and default-pool; list, inspect and
delete round-trip and leave no container behind.
|
| Filename | Overview |
|---|---|
| packages/api/src/adapter-gcp/GcpCloudSqlAdapter.ts | Cloud SQL adapter lists/creates/gets/deletes instances and re-reads after sql#operation receipts. |
| packages/api/src/adapter-gcp/GcpGkeAdapter.ts | GKE adapter uses /container/v1 paths and resolves create via targetLink re-read. |
| packages/api/src/adapter-gcp/operations.ts | Shared helpers distinguish embedded resources from SQL/GKE operation receipts. |
| packages/api/src/cloudProxy.ts | Registers GcpCloudSqlAdapter and GcpGkeAdapter in the cloud adapter registry. |
| packages/api/src/cloud-spi/databaseSchema.ts | Cloud SQL schema gains create fields, actions, columns, and capability rows. |
| packages/api/src/cloud-spi/eksSchema.ts | GKE schema gains create fields, actions, columns, and capability rows. |
Sequence Diagram
sequenceDiagram
participant UI as Cloud Explorer UI
participant API as CloudProxy / registry
participant SQL as GcpCloudSqlAdapter
participant GKE as GcpGkeAdapter
participant GCP as floci-gcp :4588
UI->>API: "create/list/get/delete database|k8s"
API->>SQL: database ops
SQL->>GCP: /sql/v1beta4/.../instances
GCP-->>SQL: "sql#operation or instance"
SQL->>GCP: GET instance (when receipt only)
API->>GKE: k8s ops
GKE->>GCP: /container/v1/.../clusters
GCP-->>GKE: Operation targetLink or cluster
GKE->>GCP: GET cluster (when receipt only)
Reviews (4): Last reviewed commit: "docs: regenerate the service matrix" | Re-trigger Greptile
f08aa4f to
975abf0
Compare
|
Good catch on the FIFO create — that was a real bug and it's fixed. The adapter accepted a Worth noting why it survived both layers of verification, since it's a trap for the other service PRs too:
So both the mock and the emulator were more forgiving than AWS, and the gap would only have shown up against the real service. Fixed by sending the attribute, with tests asserting it is set for a CI is green. |
cfaf653 to
3f927a5
Compare
hectorvent
left a comment
There was a problem hiding this comment.
Thanks for this one, and especially for catching that /v1/projects/{p}/locations/{l}/clusters on the runtime is Managed Kafka, not GKE. That is exactly the kind of trap that would have shipped looking healthy, and pinning the /container/v1/ prefix with a test plus a comment explaining why is the right way to keep it caught. operations.ts centralizing the three operation-envelope shapes is also a nice touch that the other GCP adapters can now lean on.
The split from the original bundle (messaging to #157, secrets to #156) reads well, and keeping Cloud SQL and GKE together here makes sense given they share the envelope handling.
One small behavioral question, not a blocker:
- In
GcpGkeAdapter.create,Number(input.values.initialNodeCount ?? 1)maps an empty string to0, which passes theNumber.isFinitecheck, so a blank text field could sendinitialNodeCount: 0instead of the documented default of 1. Could you treat blank (and non-positive) input as the default? Something like parsing viastringValuefirst, or guarding with> 0, would cover it, and a one-line test alongside the existing "defaults the node count" case would lock it in.
Nit, take or leave: the schemas advertise every action as available with runtimeRequired: true for create, and the PR body notes create needs the Docker socket work in #154 to actually spawn containers locally. If the runtime without #154 answers create with an error, the adapter surfaces it, so this is fine as is; just flagging in case you would rather mark create with a reason until #154 lands.
Everything else checks out: registration follows the current catalog pattern (adapters plus cloudProxy.ts, no frontend edits needed), the capability claims are backed by real adapter methods, tests assert concrete request paths and bodies rather than mocking everything away, and the README matrix was regenerated. Nice work.
GKE gained create and delete in floci-io#155, so 'k8s is read-only on every cloud' is no longer true. The AKS limitation is runtime-specific and stands on its own: floci-az cannot provision a cluster. Also renames a leftover 'machine' variable to 'cluster'.
|
🎉 This PR is included in version 0.3.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
# [0.3.0](floci-io/floci-ui@0.2.0...0.3.0) (2026-08-18) ### Bug Fixes * **docker:** frontend dev container missing rolldown native binding ([floci-io#152](floci-io#152)) ([a531a1b](floci-io@a531a1b)) * **docker:** init scripts failing on Windows due to CRLF line endings ([floci-io#138](floci-io#138)) ([238e924](floci-io@238e924)) * **docker:** mount the Docker socket so Lambda and Cloud SQL can run ([floci-io#154](floci-io#154)) ([8ee3715](floci-io@8ee3715)) * **frontend:** colour the connection dot from the connection state ([floci-io#181](floci-io#181)) ([0e0682d](floci-io@0e0682d)) * **frontend:** show app-level validation messages in dynamic create form ([floci-io#150](floci-io#150)) ([69ba389](floci-io@69ba389)), closes [floci-io#112](floci-io#112) ### Features * **api:** derive service availability from a single catalog ([floci-io#153](floci-io#153)) ([2d62143](floci-io@2d62143)) * **aws:** add API Gateway to Cloud Explorer ([floci-io#169](floci-io#169)) ([07fe1c6](floci-io@07fe1c6)) * **aws:** add DynamoDB Cloud Explorer ([floci-io#143](floci-io#143)) ([074eef3](floci-io@074eef3)) * **azure:** add Key Vault explorer ([floci-io#147](floci-io#147)) ([90dd60e](floci-io@90dd60e)) * **compute:** add an Azure Virtual Machines adapter ([floci-io#161](floci-io#161)) ([06541a5](floci-io@06541a5)) * **foundation:** add iac service-type category ([floci-io#178](floci-io#178)) ([c9e9717](floci-io@c9e9717)) * **frontend:** collapsible sidebar with icon rail ([floci-io#180](floci-io#180)) ([2f05f46](floci-io@2f05f46)) * **gcp:** add Cloud SQL and GKE adapters ([floci-io#155](floci-io#155)) ([fd3bd2f](floci-io@fd3bd2f)) * **release:** one-button release cut from main ([floci-io#187](floci-io#187)) ([d7e940b](floci-io@d7e940b)) * **s3:** add inline preview for images, video, audio and text objects ([floci-io#177](floci-io#177)) ([8b945fd](floci-io@8b945fd)) * **serverless:** Add loading, empty, and retry states to Azure Functions view ([floci-io#136](floci-io#136)) ([c4bd78f](floci-io@c4bd78f)), closes [floci-io#114](floci-io#114) * **serverless:** improve Azure Functions metadata [floci-io#113](floci-io#113) ([floci-io#140](floci-io#140)) ([bcd21a3](floci-io@bcd21a3))
Split out of #155 so each service lands on its own. Independent of #155 and #156 — this branch is off `main`. The sidebar carried a route-less **"Queue" placeholder that could never light up**, while Floci core has had three seeded SQS queues the whole time: ``` $ curl -s .../aws/services/messaging/resources | jq -r '.[]|"\(.name) msgs=\(.metadata.approximateMessages)"' orders-queue msgs=0 dead-letter-queue msgs=0 notifications-queue msgs=0 ``` One category for both SQS and Pub/Sub rather than separate queue/topic ones: the delivery semantics differ, but a console renders both the same way — a named endpoint with a depth and a few settings — so splitting them would double the nav for nothing. ## Contract details worth a look - **SQS ids.** SQS addresses queues by URL, but a URL embeds the endpoint and changes if the runtime is re-pointed, which makes it a poor stable id. The adapter uses the queue name and resolves it via `GetQueueUrl` per call, as the AWS console does. Epoch-second timestamps are converted to the ISO strings the contract expects. - **FIFO.** `Attributes.FifoQueue=true` is set for a `.fifo` name. Worth calling out because *neither* layer of verification caught its earlier absence: the unit stub accepted any input, and Floci core infers FIFO from the suffix so it worked locally too. It would only have failed against AWS. Caught in review on #155 and fixed here. - **Pub/Sub creates with `PUT`** on the resource path, not `POST` to the collection. - A queue whose `GetQueueAttributes` fails still lists, with detail omitted — a describe failure should degrade one row, not blank the table. ## One SPI change `CloudResource.type` was a closed union of eleven literals; `queue`, `fifo-queue` and `topic` are the first additions since. It is now open with a `KnownResourceType` alias documenting the convention — nothing dispatches exhaustively on it, so a closed union meant editing two packages per adapter for no safety. `CloudServiceType` stays closed, because that one is route-addressable and a typo there should 404. The identical widening also appears on #156, so whichever lands second is a no-op for that hunk. Adds `@aws-sdk/client-sqs` (both `bun.lock` and `pnpm-lock.yaml` updated — the API installs with `bun --frozen-lockfile`, which I learned the hard way). ## Verification `pnpm lint`, `type-check`, `test`, `build` pass. Live round-trip on both providers: ``` $ curl -X POST .../aws/services/messaging/resources -d '{"queueName":"probe","visibilityTimeout":"45"}' create: 201 → inspect: vis=45 arn=arn:aws:sqs:us-east-1:000000000000:probe → delete: 200 → 404 $ curl -X POST .../gcp/services/messaging/resources -d '{"topicName":"probe"}' create: 201 → list: [('probe','topic')] → delete: 200 → [] ``` README service matrix regenerated from the registry. ## Type of change - [ ] Bug fix (`fix:`) - [x] New feature / service UI (`feat:`) - [ ] Breaking change (`feat!:` or `fix!:`) - [ ] Docs / chore ## Area - [ ] Frontend (`packages/frontend`) - [x] API / Cloud Proxy (`packages/api`) - [x] Cloud Explorer adapter / schema - [ ] Build / CI / Docker ## Checklist - [x] `pnpm lint`, `pnpm type-check`, `pnpm test`, and `pnpm build` pass locally - [x] New or updated tests added where it makes sense (`bun test` in `packages/api`) - [x] No fake/mock data added — unwired states stay empty or show an explicit placeholder - [x] Commit messages / PR title follow [Conventional Commits](https://www.conventionalcommits.org/) --------- Co-authored-by: fredpena <f.ant.pena@gmail.com>
# [0.4.0](floci-io/floci-ui@0.3.0...0.4.0) (2026-09-01) ### Bug Fixes * **ec2:** include catalog AMIs in launch selector ([floci-io#191](floci-io#191)) ([b72135d](floci-io@b72135d)) ### Features * **aws:** add CloudFormation adapter to Cloud Explorer ([floci-io#184](floci-io#184)) ([f3d6105](floci-io@f3d6105)), closes [floci-io#81](floci-io#81) [floci-io#75](floci-io#75) [floci-io#81](floci-io#81) [floci-io#75](floci-io#75) [floci-io#81](floci-io#81) * **aws:** add EventBridge explorer ([floci-io#146](floci-io#146)) ([42944e9](floci-io@42944e9)), closes [floci-io#85](floci-io#85) * **aws:** add IAM to Cloud Explorer ([floci-io#145](floci-io#145)) ([fc50d19](floci-io@fc50d19)), closes [floci-io#79](floci-io#79) * **aws:** add Secrets Manager resource adapter ([floci-io#193](floci-io#193)) ([4811866](floci-io@4811866)) * **azure:** add databases and split Cosmos NoSQL ([floci-io#149](floci-io#149)) ([2e704f4](floci-io@2e704f4)), closes [floci-io#92](floci-io#92) [floci-io#67](floci-io#67) [floci-io/floci-az#138](floci-io/floci-az#138) [floci-io#143](floci-io#143) * **azure:** add Service Bus explorer ([floci-io#144](floci-io#144)) ([a9f0d06](floci-io@a9f0d06)), closes [floci-io#89](floci-io#89) * **eks:** Manage nodegroups and Fargate profiles via Cloud Proxy ([floci-io#194](floci-io#194)) ([3091cc9](floci-io@3091cc9)), closes [floci-io#106](floci-io#106) * **loadbalancing:** add an AWS Elastic Load Balancing adapter ([floci-io#168](floci-io#168)) ([10d4298](floci-io@10d4298)), closes [floci-io#162](floci-io#162) [floci-io#162](floci-io#162) [floci-io#156](floci-io#156) * **messaging:** add a messaging category with SQS and Pub/Sub ([floci-io#157](floci-io#157)) ([dfa6d1c](floci-io@dfa6d1c)), closes [floci-io#155](floci-io#155) [floci-io#155](floci-io#155) [floci-io#156](floci-io#156) [floci-io#156](floci-io#156) * **secretsmanager:** add JSON key-value editor for secret values ([floci-io#195](floci-io#195)) ([8e88961](floci-io@8e88961)), closes [floci-io#151](floci-io#151) * **ses:** Add AWS SES mailbox to Cloud Explorer ([floci-io#196](floci-io#196)) ([6389a56](floci-io@6389a56)), closes [floci-io#130](floci-io#130)
… Manager (#156) Narrowed: this is now **secrets only**, off `main`. Cloud SQL/GKE are #155 and messaging is #157 — all three independent. Both runtimes serve secret metadata, but the console had only an AWS-specific page and nothing for GCP. | Cloud | Was | Now | |---|---|---| | AWS Secrets Manager | dedicated page outside Cloud Explorer | SPI adapter; page retained for value reveal | | GCP Secret Manager | not present | list, create, inspect, delete | ## No secret value reaches a resource object Neither adapter reads a value and no schema returns one. A value on `CloudResource.metadata` would flow into the inspector, the client-side query cache and the request telemetry — reveal has to be an explicit, uncached action. Both test files assert this rather than trusting the implementation: ```ts // AWS: GetSecretValue is never sent, and no value-shaped key survives expect(sent.every((c) => c.constructor.name !== 'GetSecretValueCommand')).toBe(true) for (const key of Object.keys(resource.metadata)) expect(key.toLowerCase()).not.toContain('value') // GCP: reading a payload needs versions/latest:access — never requested expect(calls.every((c) => !c.url.includes(':access'))).toBe(true) ``` ## On the create-payload review comment Half right, and the useful half is now fixed. The premise isn't: AWS marks both `SecretString` and `SecretBinary` **optional** on `CreateSecret`, and a valueless create succeeds against the runtime too — ``` $ curl -X POST .../aws/services/secrets/resources -d '{"secretName":"floci-noval"}' create: 201 → exists: floci-noval ``` — so create wasn't failing and the matrix wasn't wrong. But a secret you can't put a value in is close to useless, and the schema offered no way to supply one. Create now accepts an optional `secretValue` and forwards it once. That direction is safe: input never lands on a resource, and there's a test asserting the value does not appear in the returned object. ## `routeByCloud` New catalog field, and the part I'd most like a second opinion on. AWS keeps its dedicated page because that's still the only way to read a value; GCP goes through the generic explorer. A single `route` per catalog entry can't express that, and pointing GCP at an AWS-only page would be worse. So the entry carries `routeByCloud: {aws: '/secretsmanager'}`, and the override disappears when the page migrates. I considered migrating the AWS page here instead, but that drops value reveal — a real regression — until the row-action mechanism exists. This felt like the honest intermediate rather than a silent capability loss. ## Small correctness note AWS delete uses `ForceDeleteWithoutRecovery`. Without it the secret enters a 30-day recovery window and keeps appearing in the list after the user deleted it, which reads as a broken delete. ## Overlap to be aware of `CloudResource.type` is widened identically here and on #157, so whichever lands second is a no-op for that hunk. ## Verification `pnpm lint`, `type-check`, `test`, `build` pass. Live round-trip on both providers, with routing resolving per cloud: ``` aws Secrets Manager available route=/secretsmanager gcp Secret Manager available route=secrets azure Secrets Manager coming_soon route=secrets ``` ## Type of change - [ ] Bug fix (`fix:`) - [x] New feature / service UI (`feat:`) - [ ] Breaking change (`feat!:` or `fix!:`) - [ ] Docs / chore ## Area - [ ] Frontend (`packages/frontend`) - [x] API / Cloud Proxy (`packages/api`) - [x] Cloud Explorer adapter / schema - [ ] Build / CI / Docker ## Checklist - [x] `pnpm lint`, `pnpm type-check`, `pnpm test`, and `pnpm build` pass locally - [x] New or updated tests added where it makes sense (`bun test` in `packages/api`) - [x] No fake/mock data added - [x] Commit messages / PR title follow [Conventional Commits](https://www.conventionalcommits.org/) --------- Co-authored-by: fredpena <f.ant.pena@gmail.com>
Narrowed: messaging moved to #157 and secrets to #156, so this is now just the two GCP services that share the operation-envelope handling. Independent of both — off
main.coming_sooncoming_soonBoth were
coming_soonbecause the frontend said so — the runtime has been serving them all along. With availability now registry-derived (#153), each is a catalog row plus an adapter and no frontend change.The thing most worth reviewing
/v1/projects/{p}/locations/{l}/clusterson floci-gcp is not GKE. It is Managed Service for Apache Kafka. It answers 200, returns objects called clusters, and creating one succeeds — so a "GKE adapter" bound there would have looked entirely healthy while listing Redpanda brokers as Kubernetes clusters.The payload and the container gave it away:
GKE lives at
/container/v1/...and behaves like Kubernetes:Worth flagging because the capability guard from #153 could not have caught it — the schema and the adapter would have agreed with each other perfectly. There is now a test asserting the adapter calls
/container/v1/, with the reason in a comment.Three operation envelopes
Google's mutations return a receipt, and the shape differs per service. All three are present on this runtime:
{done, response}sql#operation+targetId{status, operationType, targetLink}The first cut of Cloud SQL echoed the receipt and surfaced the operation UUID as the resource name.
adapter-gcp/operations.tskeeps that discrimination in one place; the two that only name the resource are read back.Cloud SQL is also PostgreSQL-only on this runtime — it rejects every other engine — so the schema offers Postgres versions rather than the full GCP list.
Verification
pnpm lint,type-check,test,buildpass. Against the live stack, both round-trip create → list → inspect → delete with no container left behind:createspawns real containers, so exercising it locally needs #154 (Docker socket). List, inspect and delete work without it, and nothing here depends on that PR to compile or test.README service matrix regenerated.
Type of change
fix:)feat:)feat!:orfix!:)Area
packages/frontend)packages/api)Checklist
pnpm lint,pnpm type-check,pnpm test, andpnpm buildpass locallybun testinpackages/api)